# PackageDebugTools.ps1
#
# Contient une suite de fonctions li?es au debug/trace
#
#Version 0.3 le 08/02/2009
#Laurent Dardenne
 param($AliasName="wp")

Write-Debug ("Call : {0}" -F $MyInvocation.MyCommand)

if ( (Test-Path variable:PscxHome) -eq $false)
 {Set-Alias rver Resolve-Error}

function Resolve-Error($ErrorRecord=$Error[0])
{
#Affiche toutes les informations de la derni?re erreur rencontr?e
 #http://blogs.msdn.com/powershell/archive/2006/12/07/resolve-error.aspx

   $ErrorRecord | Format-List * -Force
   $ErrorRecord.InvocationInfo |Format-List *
   $Exception = $ErrorRecord.Exception
   for ($i = 0; $Exception; $i++, ($Exception = $Exception.InnerException))
   {   "$i" * 80
       $Exception |Format-List * -Force
   }
}

 
function Get-CallStack{
#Auteur Bruce Payette
 #Erreur : Get-Variable : The scope number '5' exceeds the number of active scopes
trap { continue }
0..100 | foreach {
                 $var = Get-Variable -scope $_ myinvocation
                  #le dernier ?l?ment du tableau est vide
                 $var.Value.PositionMessage -replace "`n"
                 }
} #Get-CallStack

function ParseStack([string[]] $Stack)
{  #On analyse des cha?nes du type: At line:48 char:7+ level1 <<<<
   #Tableau de cha?nes provenant d'un appel ? Get-CallStack
   #Ex:     
   #     Write-Debug "Pile d'appel : $(ParseStack $(Get-CallStack))"
   
  $Result=new-object System.Text.StringBuilder
  #le premier ?l?ment du tableau contient l'appel ? Get-CallStack
  
   #Parcours inverse
  for($i=$Stack.Count-1; $i -ge 1;$i--)
  {   
     if ($Stack[$i] -match "^(.*)\+(.*) <<<<")
      {  #Ajoute un s?parateur sauf pour le premier
        if ($i -ne $Stack.Count-2) 
         {[void]$Result.Append(":")} 
        [void]$Result.Append($Matches[2].Trim()) 
      }
  }
 $Result.ToString();
} #ParseStack

 #On informe si on ?crase l'existant
 # si l'alias wp est en R/O -> Exception
if (Test-Path alias:$AliasName) 
{ 
 if ((alias $AliasName).Definition -ne "Write-Properties") 
  { Write-Warning "L'alias wp a ?t? red?fini." }
}
  
Set-Alias -name $AliasName -value Write-Properties 
function Write-Properties($From, $PropertyName ="*",[Switch] $Passthru, [Switch] $Silently)
{ #Adaptation d'un script de J.Snoover
  #Emet le contenu de toutes les propri?t?s d'un objet dans le pipeline, sur la console et sur un debugger actif
  #
  # $From         : l'objet ? interroger.
  #
  # $PropertyName : le nom de la propri?t?, ce nom peut contenir des jokers.
  #                 C'est un tableau de string 
  #
  # $Silently     : N'?met plus les informations sur la console mais seulement vers le debugger actif
  #
  # $Passthru     : Indique l'?mission des informations dans le pipeline.
  #                 par d?faut elle ne sont ?misent que sur la console et sur un debugger actif.
  #
  #Exemples :
  # $a=dir
  # Write-Properties $a[-1] 
  # Write-Properties $a[-1] |%{Write-host $_ -fore DarkGreen}
  # $Res=Write-Properties $a[-1] 
  # wp $A[-1] Name,Extension,L*
  
  begin
  {
    $IsDebuggerAttached=[System.Diagnostics.Debugger]::IsAttached
    $CS="Call : {0}" -F $MyInvocation.InvocationName
    Write-Debug $CS
    
    function WriteProperties{

        if ($IsDebuggerAttached) # faire [System.Diagnostics.Debugger]::Launch()
        {
         $sbDbgWrite={[System.Diagnostics.Debug]::WriteLine($Args[0])} #Pour Visual Studio
         [System.Diagnostics.Debug]::Indent()
        }
        else {$sbDbgWrite={[System.Diagnostics.Debug]::Write($Args[0])}} #Pour DbgView
         #La chaine $CS est envoy? vers le debugger actif
        &$sbDbgWrite $CS
        
        foreach ($P in Get-Member -In $From -MemberType *Property -Name $propertyName|Sort name)
        {  
          $Result ="$($P.Name) : $($From.$($P.Name))"
          
           #Affiche ou non sur la console.
           #Dans tous les cas on affiche sur le d?bugger
          if (!$Silently) {Write-Host $Result}
             
          if ((get-Variable psversiontabl[e]) -ne $null)   
          {   #CommandLineParameters en PS v2 seulement
            $Source=$MyInvocation.CommandLineParameters."From".ToString()
            $Result="$Source : $Result"
          }
         &$sbDbgWrite $Result
         if ($Passthru) {$Result}
        }
       if ($IsDebuggerAttached)
        {[System.Diagnostics.Debug]::Unindent() }     
    }
  }
  process
  {
     if ($_)
     {   
       WriteProperties -From $_ $PropertyName -Silently:$Silently 
       $_
     }
  }
  end
  {
     if ($From)
      { WriteProperties -From $From $PropertyName -Silently:$Silently }
  } 
} #Write-Properties


function Test-Variable([System.Management.Automation.PSVariable] $Variable=
                        $(Throw "Test-Variable : le param?tre Variable, de type PSVariable, doit ?tre renseign?."),
                       [Type] $Type=$(Throw "Test-Variable : le param?tre Type doit ?tre renseign?."), 
                       [Switch] $Strict,
                       [Switch] $TestEmptyString)
{  # L?ve une exception si le contenu de $Variable est ? $Null ou ne r?pond pas aux crit?res suivants :

   # Si la validation demand?e est stricte et que le type du param?tre est du type attendu -> FIN 
   # Si la validation demand?e est stricte et que le type du param?tre n'est pas du type attendu -> Exception

   # Si la validation demand?e n'est pas stricte, que le type du param?tre est du type attendu et
   # que le cast r?ussi -> FIN
   # Si la validation demand?e n'est pas stricte, que le type du param?tre n'est pas du type attendu et
   # que le cast ?choue -> Exception 
   #  note : avec le type [string] dans ce cas le cast r?ussi toujours. Avec un number vers une Date aussi, etc.
   #
   # Si $TestEmptyString est indiqu? et que $Type est ?gal ? [String] on valide le contenu du param?tre de type cha?ne 
   # S'il est vide -> Exception  
   # sinon ->Suite
   #
   # Exception : ArgumentTransformationMetadataException 
   #
   # Test-Variable (gv NomDeVariable) String -strict -TestEmptyString
   # Test-Variable (gv NomDeVariable) DateTime  -TestEmptyString
   
   #Todo localisation
   
    Write-Debug ("Call : {0}" -F $MyInvocation.InvocationName)
     #r?cup?re le contexte d'appel de l'appelant
    $ParentInvocation = (Get-Variable MyInvocation -Scope 1).Value
    $CS="{0}.{1}" -F $ParentInvocation.MyCommand,$MyInvocation.MyCommand
    
    if ($Variable -eq $null) {Throw "$CS : le param?tre Variable est ? null."}
    if ($Type -eq $null) {Throw "$CS : le param?tre Type est ? null."}                     
    Write-debug $Variable.Name
    if ($Variable.Value -ne $null) 
     {Write-debug "$($Variable.Value.GetType())"}
    else 
     {
       Write-debug "`Le param?tre $($Variable.Name) est ? `$null."
       Throw "$CS : La valeur du param?tre $($Variable.Name) est `$null."
     }
    Write-debug "$Variable.Value"
    Write-debug "$Type"
    if ($TestEmptyString -and ($Type -eq [String]) )
     {if ($Variable.Value -eq [string]::Empty)
       {Throw "$CS : Le contenu du param?tre $($Variable.Name) n'est pas renseign?."}
     } 
    #$host.EnterNestedPrompt()

     #Le param?tre re?u est-il du type attendu ?
    $isSameType=$Variable.Value -is $Type
    
     #Peut-on caster la valeur re?ue dans le type attendu ?
     #En cas de cast impossible, l'op?rateur -as appel la m?thode ToString()
     #Ce qui reste coh?rent avec le parsing de PowerShell.
     #Si le param?tre attendu est de type [string], dans ce cas une date sera transform?e en string.
     #Le cast du param?tre en un type [string] r?ussira toujours !
    $isCastFail=($Variable.Value -as $Type) -eq $null
    
    Write-debug "$isNotSameType : $isNotSameType"
    Write-debug "isCastFail : $isCastFail"
    
     #Si le cast demand? est strict, le type du param?tre doit ?tre celui du type attendu
     #ou 
     #si le cast demand? n'est pas strict que le type du param?tre n'est pas celui du type attendu et que le cast ?choue.
    if ( ($Strict.IsPresent -and !$isSameType) -or (!$Strict.IsPresent -and !$isSameType -and $isCastFail) ) 
      { 
        $StrFrm="$CS : Impossible de convertir la valeur du param?tre {0} ? {1} ? en type ? {2} ?."
        $Ex= new-object System.Management.Automation.ArgumentTransformationMetadataException ( 
              $StrFrm -F $Variable.Name,$Variable.Value.GetType(),$Type.ToString() ) 
       throw $Ex
      }   
} #Test-Variable 

Function Get-CallersObject(){
 #R?cup?re des informations des appelants en parcourant la pile d'appel sur 2 niveaux
 #La function courante n'est pas prise en compte dans le r?sultat
 #Renvoi un objet personnalis?   
 #Redondant avec ParseStack $(Get-CallStack)

 $Callers= new-object System.Management.Automation.PsObject
  #A.B.C  o? C est la function courante, c'est ? dire Get-CallersObject
  
 $Callers|`
    #My contient le nom de la function appelant cette function, c'est ? dire la fonction B
   add-member NoteProperty My -value ((Get-Variable MyInvocation -Scope 1).Value) -passthru |`
    #Parent contient le nom de la function la function My, c'est ? dire la fonction A
   add-member NoteProperty Parent -value ((Get-Variable MyInvocation -Scope 2).Value) -passthru |`
   add-member NoteProperty Format -value "{0}.{1}" -passthru |`
   add-member -Force ScriptMethod ToString{
    $($this.Format) -F $this.Parent.MyCommand,$this.My.MyCommand
   }
 return $Callers
 #Exemple : 
#    function Parent{
#     function Enfant {
#       function PetitEnfant {
#        Write-Host "----"
#        $S=Get-CallersObject
#        $S.ToString()
#        $S.My
#        $S.Parent
#       } #PetitEnfant
#
#      Write-Host "----"
#      $R=Get-CallersObject
#      $R.ToString()
#      $R.My
#      $R.Parent
#       
#      PetitEnfant
#    } #Enfant
#    Enfant
#   } #Parent
#   
#   Parent 
}

New-Alias ViewSegment Create-ViewSegmentSB
New-Alias VwSg Create-ViewSegmentSB

function Create-ViewSegmentSB([String] $VariableName=$(throw "Le nom de la variable doit ?tre pr?cis?.") )
{ #Cr?e un scriptbloc, d?clarant les trois blocs de code Begin, Process et End, utilis? 
  # ? des fins de debug ($DebugPreference="Continue")
  #
  # L'instruction suivante affiche le nombre d'?l?ments re?us du segment pr?c?dent.
  # 1,2|Foreach {$I=0} {$I++;$_} {if ($I -eq 0) {Write-Debug "Segment 1 : aucun ?l?ments."} 
  #                               else {Write-Debug "Segment 1 : $I ?l?ments trouv?s."}}
  # 1
  # 2
  # DEBUG: Segment 1 : 2 ?l?ments trouv?s.
 
  #Il reste possible, en lieu et place de cette fonction, d'utiliser un scriptblock d?clarant uniquement  
  #le bloc End, dans ce casla variable $input contient la totalit? des objets ?mis par le segment pr?c?dent.  
  # par exemple :
  #
  # 1,2|&{ End {$I=($input|% {$_}).count;if ($i -eq $null) {$I=0}; Write-Debug " $I ?l?ments trouv?s."} }
  # 1
  # 2
  # DEBUG: 2 ?l?ments trouv?s.
  #
  # $sb={ End {$I=($input|% {$_}).count;if ($i -eq $null) {$I=0}; Write-Debug " $I ?l?ments trouv?s."} }
  # @()|&$sb
  # DEBUG: 0 ?l?ments trouv?s. 
  #
  #Autres exemple :
  #   1..20|&$(ViewSegment "Premier")|`
  #     #Filtre les nombres pairs
  #    Where{ !($_ -band 1) }|&$(ViewSegment "Pairs")|`
  #     #Filtre les nombres divisible par cinq
  #    Where{ ($_ % 5) -eq 0 }|&$(ViewSegment "DivisibleParCinq")  


  #Tests de validit? du nom de variable
  if ($VariableName -eq [String]::Empty)
  {throw "Le nom de la variable locale n'est pas renseign?."} 
  
  if ($VariableName.Contains('$') )
   {throw "L'expansion de variable n'est pas support?e."} 
  
   #Stop le pipeline 
  trap {break }
   &{  #G?re l'erreur du scriptblock de validation du nom de variable
      trap { Throw "Le nom de variable ( $VariableName ) pose probl?me." }
         #Test la validit? du nom de variable
         #  Si VariableName="Test" alors on ex?cute l'instruction $Test=0 et ce sans erreur 
         #  Si VariableName="Te/st" alors on ex?cute l'instruction $Te/st=0 ce qui provoque 
         #  une erreur de syntaxe dans le scriptbloc, cette erreur est propag?e vers l'appelant.
         #Au lieu de tester les diff?rentes possibilit?s on g?n?re un code de test
         # puis on l'ex?cute.
       &$($ExecutionContext.InvokeCommand.NewScriptBlock("`$$VariableName=0"))
    }
    
  #On g?n?re le code du scriptbloc ? l'aide de l'expansion de variable, 
  # puis on cr?e un scriptbloc qui est renvoy? dans le pipeline.
  #
  #L'appel du scriptblock g?n?r? se fera ainsi :
  #   1,2|&$(Create-ViewSegmentSB "I" )
  # ou
  #   $D=Create-ViewSegmentSB "PremierSegment" 
  #   1,2|&$D "H" 
  # ou encore
  #   New-Alias ViewSegment Create-ViewSegmentSB
  #   1,2|&$(ViewSegment "I")
  # New-Alias vwsg Create-ViewSegmentSB
  #   1,2|&$(vwsg "I")
  # renvoi :
  # 1
  # 2
  # DEBUG: Segment I (1) : 5 ?l?ments trouv?s.
  #
  #O? I est le nom de la variable (le "nom" du segment)
  # et (1) la position du segment courant
 $ExecutionContext.InvokeCommand.NewScriptBlock(@"
       #Initialise la variable
       # Si $VariableName="I", pour {`$$VariableName=0} on g?n?re {$I=0}
      Begin {`$$VariableName=0}
       #incr?mente le compteur puis r??met l'objet
      Process {`$$VariableName++;`$_}
       #Affichage final
      End {if (`$$VariableName -eq 0) 
               # $MyInvocation.PipeLinePosition permet de r?cup?rer la position du segment courant
               # dans le pipeline
            { Write-Debug `"Segment $VariableName (`$(`$MyInvocation.PipeLinePosition)/`$(`$MyInvocation.PipeLineLength)) : aucun ?l?ments.`"} 
           else
            { Write-Debug `"Segment $VariableName (`$(`$MyInvocation.PipeLinePosition)/`$(`$MyInvocation.PipeLineLength)) : `$$VariableName ?l?ments trouv?s.`"}            
          }
"@
  )
}

